﻿#region Using directives

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

using Weazel.Math;

#endregion

namespace Weazel.Gezel.Simulator.Reference.IpBlocks
{
  [IpBlockAttribute("ijvm")]
  public class IjvmBlock : AbstractIpBlock
  {
    private const int PinAddress = 0;
    private const int PinWr = 1;
    private const int PinRd = 2;
    private const int PinIData = 3;
    private const int PinOdata = 6;
    private const int PinBytes = 4;
    private const int PinFetch = 5;
    private const int PinByteval = 7;

    private struct IjvmDataBlock
    {
      public uint   Start;
      public uint   Length;
      public byte[] Data;     
    }

    private List<Integer> content = new List<Integer>();
    private int size;
    private int sp;
    private int lv;
		private int cpp;
    private string filename;

    public IjvmBlock(string name) 
			: base(name) 
    {
    }

    // variables to help us know when to initialize
    private bool sizeSet = false;
    private bool fileSet = false;
    private bool spSet = false;
    private bool lvSet = false;
    private bool cppSet = false;

    public override void SetParameter(string _name)
    {
      string field = _name.Substring(0, _name.IndexOf('='));
      string value = _name.Substring(_name.IndexOf('=') + 1);

      field = field.Trim();
      value = value.Trim();

      switch (field)
      {
        case "size":
          this.size = new Integer(32, value, false).ToInt();
          this.sizeSet = true;
          break;
        case "file":
          this.filename = value;
          this.fileSet = true;
          break;
        case "sp":
          this.sp = new Integer(32, value, false).ToInt();
          this.spSet = true;
          break;
        case "lv":
          this.lv = new Integer(32, value, false).ToInt();
          this.lvSet = true;
          break;
        case "cpp":
          this.cpp = new Integer(32, value, false).ToInt();
          this.cppSet = true;
          break;
        default:
          throw new InvalidOperationException("invalid parameter '" + field + "'");
      }

      // if we have size and filename set
      // we can initialize its contents
      if (sizeSet && fileSet) // && spSet && lvSet && cppSet)
      {
        initialize();
      }
    }

    public override bool CheckTerminal(int n, string tname, AbstractIpBlock.IoDirection dir)
    {
      switch (n)
      {
        case PinAddress:
          return (dir == AbstractIpBlock.IoDirection.Input && tname == "address");
        case PinWr:
          return (dir == AbstractIpBlock.IoDirection.Input && tname == "wr");
        case PinRd:
          return (dir == AbstractIpBlock.IoDirection.Input && tname == "rd");
        case PinIData:
          return (dir == AbstractIpBlock.IoDirection.Input && tname == "idata");
        case PinOdata:
          return (dir == AbstractIpBlock.IoDirection.Output && tname == "odata");
        case PinBytes:
          return (dir == AbstractIpBlock.IoDirection.Input && tname == "bytes");
        case PinFetch:
          return (dir == AbstractIpBlock.IoDirection.Input && tname == "fetch");
        case PinByteval:
          return (dir == AbstractIpBlock.IoDirection.Output && tname == "byteval");         
      }
      return false;
    }
    
    public override void Run() {
      // perform ram function using 
      //   io vector       #
      //   -----------------------------
      //   in address      0
      //   in wr           1
      //   in rd           2
      //   in idata        3
      //   out odata       4
      //   in bytes        5
      //   in fetch        6
      //   out byteval     7
  
      if (ioValues[PinRd].ToUInt() == 1)
      {
        // memory mapped io (read one character)
        if (ioValues[PinAddress].ToUInt() == 0xfffffffd)
        {
          this.OutputBuffer.Append("Asking for input of one character: ");					

					// todo: this is NOT possible from within TOAD!
					int value = Console.Read();

          ioValues[4] = new Integer(32, value, false);
        }
        else if (ioValues[PinAddress].ToUInt() > content.Count)
        {
          string message = string.Format(
            "Error: Address value for ram ip block out-of-range 0x{0:X} ({1})",
            ioValues[0].ToInt(),
            ioValues[0].ToInt()
          );

          throw new IpBlockIndexOutOfRangeException(message);        
        }
        else
        {          
          ioValues[PinOdata].ValueCopy(content[ioValues[PinAddress].ToInt()]);
        }        
      }

      if (ioValues[PinWr].ToInt() == 1)
      {
        // memory mapped io .. (write one char to console)
        if (ioValues[PinAddress].ToUInt() == 0xfffffffd)
        {
					this.OutputBuffer.Append((char)(0xff & ioValues[3].ToInt()));
        }
        else if (ioValues[PinAddress].ToInt() >= content.Count)
        {
          string message = string.Format(
            "Error: Address value for ram ip block out-of-range 0x{0:X} ({1})",
            ioValues[0].ToInt(), 
            ioValues[0].ToInt()
          );

          throw new IpBlockIndexOutOfRangeException(message);
        }
        else
        {
          content[ioValues[PinAddress].ToInt()].ValueCopy(ioValues[PinIData]);
        }
      }

      if (ioValues[PinFetch].ToInt() == 1)
      {
        if (ioValues[PinBytes].ToInt() > content.Count)
        {
          string message = string.Format(
            "Error: Address value for ram ip block out-of-range 0x{0:X} ({1})",
            ioValues[PinBytes].ToInt(), 
            ioValues[PinBytes].ToInt()
          );

          throw new IpBlockIndexOutOfRangeException(message);
        }				
        else
        {          
          // read!
          ioValues[PinByteval].ValueCopy(content[ioValues[PinBytes].ToInt()]);
        }
      }
    }

    private uint bufferToUint32(byte[] buffer, int startIndex)
    {
      uint result =
        (uint)buffer[startIndex + 0] << 24 |
        (uint)buffer[startIndex + 1] << 16 |
        (uint)buffer[startIndex + 2] << 8 |
        (uint)buffer[startIndex + 3];

      return result;
    }

    private void getBlock(FileStream stream, ref IjvmDataBlock block)
    {
      // read start address
      byte[] buffer = new byte[4];
      int bytesRead = stream.Read(buffer, 0, 4);

      if (bytesRead < 4)
        throw new System.IO.FileLoadException("Error loading file, probably not an '.Ijvm' file", filename);

      block.Start = bufferToUint32(buffer, 0);

      // read length
      bytesRead = stream.Read(buffer, 0, 4);    

      if (bytesRead < 4)
        throw new System.IO.FileLoadException("Error loading file, probably not an '.Ijvm' file", filename);

      block.Length = bufferToUint32(buffer, 0);

      if (block.Start % 4 != 0)
        throw new FileLoadException("Start address must be a multiplum of 4", filename);

      if (block.Length > 0)
      {
        // set size of buffer to hold an exact number of words (possibly more than len)
        int size = block.Length % 4 == 0 ? 
            size = (int)block.Length 
         :  size = ((int)block.Length / 4) * 4 + 4;        

        block.Data = new byte[size]; 
        bytesRead = stream.Read(block.Data, 0, (int) block.Length);
        
        if (bytesRead != block.Length)
          throw new FileLoadException("Error loading file, wrong number of bytes returned", filename);

        block.Length = (uint)size;
      }
      else
      {        
        block.Data = null;
      }
    }

    private int InitializeSection(int first, ref IjvmDataBlock block)
    {     
      int wordCount = 0;
      
      for (int i = first + 1; i <= block.Start / 4; i++)
      {
        content.Add(new Integer("0xdeadbeef"));
        wordCount++;
      }
      
      // convert from 4bytes to 32 bit Integer
      for (int i = 0; i < block.Length / 4; i ++)
      {       
        uint value = bufferToUint32(block.Data, i * 4);
        content.Add(new Integer(32, value, false));

        wordCount++;
      }
      return wordCount;
    }
  
    private void initialize()
    {
      // remove whitespace etc...
      filename = filename.Trim();

      FileStream stream = null;

      // try to open the file
      try
      {
        stream = File.Open(filename, FileMode.Open);
      }
      catch (System.IO.FileNotFoundException e)
      {
        // todo: insert some kind of error handling scheme..
        throw e;
      }

      byte[] buffer = new byte[4];
      int bytesRead = stream.Read(buffer, 0, 4);

      if (bytesRead < 4) // this is a problem..
        throw new System.IO.FileLoadException("Failed to read 4 bytes from file", filename);
      
      IjvmDataBlock block1 = new IjvmDataBlock();
      IjvmDataBlock block2 = new IjvmDataBlock();

      getBlock(stream, ref block1);
      getBlock(stream, ref block2);

      stream.Close();

      if (block1.Start > block2.Start)
      {
        // swap
        IjvmDataBlock tmp = block1;
        block1 = block2;
        block2 = tmp;
      }

      block2.Start = (uint) (4 * cpp);

			if (block1.Start + block1.Length > block2.Start)
			{
				string message = 
					string.Format(
						"Inconsistent block information. Block 1 start: {0}, Block 1 length: " +
						"{1}, Block 2 start: {2}, Block 2 length: {3}",
						block1.Start,
						block1.Length,
						block2.Start,
						block2.Length
					);

				throw new FileLoadException(message, filename);
			}

      int wordCount = InitializeSection(0, ref block1);
      wordCount = InitializeSection(wordCount, ref block2);

      int start = (int)(block2.Start + block2.Length) / 4 + 1;

      // fill with 'default' value
      for (int i = start; i < size; i++)
        content.Add(new Integer(32, "0xdeadbeef", false));
    }
  }
}
